This repository has no description
1import { error, redirect } from '@sveltejs/kit';
2import { createBobbinClient } from '$lib/api/client';
3import { resolveMiniDoc } from '$lib/api/identity';
4import { getProfile, type ProfileRecord } from '$lib/api/records';
5import { count } from '$lib/api/count';
6import { parallel, toHttpError, httpStatusFor } from '$lib/api/load';
7import { ClientResponseError } from '$lib/api/client';
8import { findFollowRkey } from '$lib/api/graph';
9import type { ProfileCounts } from '$lib/components/profile/types';
10import type { LayoutLoad } from './$types';
11
12export const load: LayoutLoad = async (event) => {
13 const parent = await event.parent();
14 const identifier = decodeURIComponent(event.params.handle);
15
16 // actor identifiers are dids or dotted handles; reject bare words early so
17 // unrelated paths (/settings, /signup, ...) 404 instead of resolving.
18 if (!identifier.startsWith('did:') && !identifier.includes('.')) {
19 error(404, 'Not found');
20 }
21
22 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch });
23 const doc = await resolveMiniDoc(ctx, identifier).catch((cause) =>
24 toHttpError(cause, 'Could not resolve user')
25 );
26
27 // canonical url is the handle; redirect dids and stale handles.
28 const canonical = doc.handle && !doc.handle.endsWith('.invalid') ? doc.handle : null;
29 if (canonical && identifier.toLowerCase() !== canonical.toLowerCase()) {
30 redirect(307, `/${canonical}${event.url.search}`);
31 }
32
33 const did = doc.did;
34 const viewerDid = parent.auth?.did;
35
36 let profile: ProfileRecord | null = null;
37 try {
38 profile = (await getProfile(ctx, did)).value;
39 } catch (cause) {
40 if (!(cause instanceof ClientResponseError && httpStatusFor(cause) === 404)) {
41 toHttpError(cause, 'Could not load profile');
42 }
43 }
44
45 const raw = await parallel({
46 repos: count(ctx, 'sh.tangled.repo.countRepos', did),
47 strings: count(ctx, 'sh.tangled.string.countStrings', did),
48 stars: count(ctx, 'sh.tangled.feed.countStarsBy', did),
49 followers: count(ctx, 'sh.tangled.graph.countFollows', did),
50 following: count(ctx, 'sh.tangled.graph.countFollowsBy', did),
51 vouches: count(ctx, 'sh.tangled.graph.countVouches', did),
52 viewerFollowRkey:
53 viewerDid && viewerDid !== did
54 ? findFollowRkey(ctx, viewerDid, did).catch(() => null)
55 : Promise.resolve(null)
56 });
57
58 const counts: ProfileCounts = {
59 repos: raw.repos.count,
60 strings: raw.strings.count,
61 stars: raw.stars.count,
62 followers: raw.followers.count,
63 following: raw.following.count,
64 vouches: raw.vouches.count
65 };
66
67 const notJoined = !profile && Object.values(counts).every((n) => n === 0);
68
69 return {
70 identity: { did, handle: doc.handle, avatar: doc.avatar },
71 profile,
72 counts,
73 viewerFollowRkey: raw.viewerFollowRkey,
74 notJoined
75 };
76};